from HTMLParser import HTMLParser
from htmlentitydefs import name2codepoint

'''
<!DOCTYPE aerisprint>
COMMANDS
initialize
cashdrawer
cut
feed

output
raw
test print

LAYOUT
endline				<br />
tab					<tab />
image				<img src="..." double-width="<True, False>" double-height="<True, False>" />
barcode				<barcode code="..." type="<UPC-A, UPC-E, EAN, CODE128>" mode="<A, B, C>" />
qrcode				<qrcode code="..." double-width="<True, False>" double-height="<True, False>" />
set horizontal		<position absolute="..." />
move horizontal		<position relative="..." />

SETUP			<?print attr=value ... />
tab stops			tabstops = 1, 2, 3, ...
char spacing		char-spacing = n
line spacing		line-spacing = n
unit of measurement	unit = n
left margin			margin = n

STYLES
alignment			align = <left, center, right>
narrow				narrow = <True, False>
bold				bold = <True, False>				<b></b>
double height		double-height = <True, False>		<h3></h3>
double width		double-width = <True, False>		<h2></h2>
underline			underline = <True, False, double>	<u></u>		<u double="True"></u>
rotation			rotate = <True, False>
flip				flip = <True, False>
inverted			invert = <True, False>
smoothing			smooth = <True, False>

'''

class HTML2Print(HTMLParser):
	
	_join = ''.join
	
	tag_stack_default = {}
	attr_stack_default = {
		'align': 'left',
		'narrow': False,
		'bold': False,
		'double-height': False,
		'double-width': False,
		'underline': False,
		'rotate': False,
		'flip': False,
		'invert': False,
		'smooth': True,
	    'color': 'black'
	}
	
	def __init__(self, target, *args, **kwargs):
		HTMLParser.__init__(self, *args, **kwargs)
		self.target = target
	
	def __call__(self, html):
		if isinstance(html, unicode):
			html = self.target.convert_to_bytes(html)
		return self.handle(html)
	
	@property
	def data(self):
		data = self._join(self._data)
		if isinstance(data, unicode):
			data = data.encode('utf8')
		return data
	
	@property
	def current_attrs(self):
		return {k:v[-1] for k, v in self._attr_stack.iteritems()}
	
	def handle(self, html):
		self.reset()
		self.feed(html)
		return self.close()
	
	def reset(self):
		HTMLParser.reset(self)
		self._doctype = None
		self._data = []
		self._tag_stack = self.tag_stack_default.copy()
		self._attr_stack = {k:[v, ] for k, v in self.attr_stack_default.iteritems()}
	
	def close(self):
		if hasattr(self.target, 'finish'):
			return self.target.finish(self.data)
		return self.data
	
	def out(self, text, attrs=None):
		self._data.append(self.target.htmlout(text))
	
	def endline(self):
		if hasattr(self.target, 'endline'):
			return self.target.endline()
		return '\r\n'
	
	def tab(self):
		if hasattr(self.target, 'tab'):
			return self.target.tab()
		return '\t'
	
	def handle_tag(self, tag, attrs):
		data = ''
		
		if tag == 'br':
			data = self.endline()
		elif tag == 'tab':
			data = self.tab()
		elif tag == 'h1':
			attrs['double-width'] = True
			attrs['double-height'] = True
		elif tag == 'h2':
			attrs['double-width'] = True
		elif tag == 'h3':
			attrs['double-height'] = True
		elif tag == 'b':
			attrs['bold'] = True
		elif tag == 'u':
			attrs['underline'] = True
			if 'double' in attrs:
				attrs['underline'] = 'double'
				del attrs['double']
		
		return self.target.handle_tag(data, tag, attrs)
	
	def handle_tag_close(self, tag, attrs):
		data = ''
		line_tags = ('h1', 'h2', 'h3', 'p')
		
		if tag in line_tags:
			data = self.endline()
			
#		if tag == 'p' and 'align' in attrs:
			if 'align' in attrs:
				data += self.handle_attrs({'align': ('left', attrs['align'])}, False)
		
		return self.target.handle_tag_close(data, tag, attrs)
	
	def open_tag(self, tag, attrs):
		data, attrs = self.handle_tag(tag, attrs)
		
		if not tag in self._tag_stack:
			self._tag_stack[tag] = [attrs, ]
		else:
			self._tag_stack[tag].append(attrs)
		
		self.open_attrs(attrs)
		if data:
			self.out(data)
	
	def close_tag(self, tag):
		# del self._tag_stack[tag][-1]
		# attrs = self._tag_stack[tag] and self._tag_stack[tag][-1] or {}
		attrs = self._tag_stack[tag].pop()
		
		data, attrs = self.handle_tag_close(tag, attrs)
		
		if data:
			self.out(data, attrs)
		self.close_attrs(attrs)
	
	def open_attrs(self, attrs):
		h_attrs = {}
		for name, value in attrs.iteritems():
			oldvalue = self._attr_stack[name][-1] if name in self._attr_stack else None
			self._attr_stack[name].append(value)
			h_attrs[name] = (value, oldvalue)
		self.handle_attrs(h_attrs)
	
	def close_attrs(self, attrs):
		h_attrs = {}
		for name, value in attrs.iteritems():
			del self._attr_stack[name][-1]
			oldvalue = self._attr_stack[name][-1]
			h_attrs[name] = (oldvalue, value)
		self.handle_attrs(h_attrs)
	
	def handle_attrs(self, attrs, output=True):
		data = self.target.handle_attrs({k:v[0] for k, v in attrs.iteritems() if v[0] != v[1]}, self.current_attrs)
		if output and data:
			self.out(data)
		return data
	
	def handle_params(self, params):
		data = []
		for name, value in params.iteritems():
			data.append(self.target.handle_param(name, value))
		if data:
			self.out(self._join(data))
	
	
	def handle_starttag(self, tag, attrs):
		self.open_tag(tag, dict(attrs))
	
	def handle_endtag(self, tag):
		self.close_tag(tag)
	
	def handle_data(self, data):
		self.out(data.replace('\n', '').replace('\r', ''))
	
	def handle_entityref(self, name):
		self.out(unichr(name2codepoint[name]))
	
	def handle_charref(self, name):
		if name[0] == 'x':
			name = name[1:]
			char = unichr(int(name, 16))
		else:
			char = unichr(int(name))
		self.out(char)
	
	def handle_comment(self, data):
		'''Ignore comments.'''
		pass
	
	def handle_decl(self, data):
		'''Handle declarations.'''
		if data.strip().lower() == 'doctype aerisprint':
			self._doctype = 'aerisprint'
			self.out(self.target.handle_attrs(self.current_attrs, self.current_attrs))
		else:
			raise GryphusError('invalid template declaration: {}'.format(data))
	
	def handle_pi(self, data):
		'''Handle processing instructions.'''
		tag, _, paramlist = data.partition(' ')
		if tag == 'print':
			params = {k:v.strip('"') for k, v in re.findall(r'(\S+)\s*=\s*(".*?"|\S+)', paramlist)}
			self.handle_params(params)
	
	def unknown_decl(self, data):
		'''Ignore unknown declarations.'''
		pass